
using System;
using System.Collections.Generic;
using System.Text;
using ScrewTurn.Wiki.PluginFramework;

namespace ScrewTurn.Wiki.PluginPack {

	[Serializable]
	public class SandboxPlugin : IPagesStorageProvider {

		private IHost host;

		private ComponentInformation info = new ComponentInformation("Sandbox Plugin 1.0.1", "ScrewTurn Software", "http://www.screwturn.eu");

		private List<PageInfo> pages;
		private Dictionary<PageInfo, List<PageContent>> content;
		private List<CategoryInfo> categories;
		private Dictionary<PageInfo, List<Message>> messages;
		private int freeId = 0;

		public void Init(IHost host, string config) {
			this.host = host;

			pages = new List<PageInfo>();
			content = new Dictionary<PageInfo, List<PageContent>>();
			categories = new List<CategoryInfo>();
			messages = new Dictionary<PageInfo, List<Message>>();

			config = config.Trim();
			if(config.Length == 0) config = "Sandbox, public";

			config = config.Replace("\r", "");

			string[] lines = config.Split(new char[] { '\n' }, StringSplitOptions.RemoveEmptyEntries);
			string[] fields;
			for(int i = 0; i < lines.Length; i++) {
				fields = lines[i].Split(',');
				for(int k = 0; k < fields.Length; k++) fields[k] = fields[k].Trim();
				if(FindPage(fields[0]) != null) throw new Exception();
				PageStatus s = PageStatus.Public;
				if(fields.Length >= 1) {
					switch(fields[1].ToLowerInvariant()) {
						case "public":
							s = PageStatus.Public;
							break;
						case "locked":
							s = PageStatus.Locked;
							break;
						case "normal":
							s = PageStatus.Normal;
							break;
					}
				}
				PageInfo p = AddPage(fields[0], DateTime.Now);
				SetStatus(p, s);
				ModifyPage(p, "The Sandbox", "SYSTEM", DateTime.Now, "", "This is a Sandbox Page; you can use it to perform tests.{BR}\n'''Note''': this page is never stored on disk, therefore your changes will be lost after a while.", false);
			}
		}

		public void Shutdown() { }

		private PageInfo FindPage(string name) {
			PageInfo p = new PageInfo(name, this, PageStatus.Normal, DateTime.Now);
			PageNameComparer comp = new PageNameComparer();
			for(int i = 0; i < pages.Count; i++) {
				if(comp.Compare(p, pages[i]) == 0) return pages[i];
			}
			return null;
		}

		public ComponentInformation Information {
			get { return info; }
		}

		public bool ReadOnly {
			get { return false; }
		}

		public PageInfo[] AllPages {
			get { return pages.ToArray(); }
		}

		public CategoryInfo[] AllCategories {
			get { return categories.ToArray(); }
		}

		private CategoryInfo FindCategory(string name) {
			CategoryNameComparer comp = new CategoryNameComparer();
			CategoryInfo tmp = new CategoryInfo(name, this);
			for(int i = 0; i < categories.Count; i++) {
				if(comp.Compare(tmp, categories[i]) == 0) return categories[i];
			}
			return null;
		}

		public CategoryInfo AddCategory(string name) {
			CategoryInfo c = new CategoryInfo(name, this);
			c.Pages = new string[0];
			categories.Add(c);
			return c;
		}

		public CategoryInfo RenameCategory(CategoryInfo category, string newName) {
			if(FindCategory(newName) != null) return null;
			category.Name = newName;
			return category;
		}

		public bool RemoveCategory(CategoryInfo category) {
			return categories.Remove(category);
		}

		public CategoryInfo MergeCategories(CategoryInfo source, CategoryInfo destination) {
			List<string> allPages = new List<string>();
			allPages.AddRange(source.Pages);
			allPages.AddRange(destination.Pages);
			destination.Pages = allPages.ToArray();
			categories.Remove(source);
			return destination;
		}

		public PageContent GetContent(PageInfo page) {
			List<PageContent> c = null;
			if(content.TryGetValue(page, out c)) {
				if(c.Count > 0) return c[0];
				else return null;
			}
			else return null;
		}

		public List<int> GetBackups(PageInfo page) {
			List<PageContent> c = null;
			if(content.TryGetValue(page, out c)) {
				if(c.Count > 1) {
					List<int> res = new List<int>(c.Count - 1);
					for(int i = 0; i < c.Count - 1; i++) {
						res.Add(i);
					}
					return res;
				}
				else return new List<int>();
			}
			else return new List<int>();
		}

		public PageContent GetBackupContent(PageInfo page, int revision) {
			List<PageContent> c = null;
			if(content.TryGetValue(page, out c)) {
				if(c.Count > 1 && revision <= c.Count - 2) {
					return c[revision + 1];
				}
				else return null;
			}
			else return null;
		}

		public bool SetBackupContent(PageContent ctn, int revision) {
			List<PageContent> c = null;
			if(content.TryGetValue(ctn.PageInfo, out c)) {
				if(revision > c.Count - 1) return false;
				c[revision] = ctn;
				return true;
			}
			else return false;
		}

		public bool Backup(PageInfo page) {
			List<PageContent> c = null;
			if(content.TryGetValue(page, out c)) {
				if(c.Count > 0) {
					PageContent tmp = new PageContent(page, c[0].Title, c[0].User, c[0].LastModified, c[0].Comment, c[0].Content);
					c.Add(tmp);
					return true;
				}
				else return false;
			}
			else return false;
		}

		public PageInfo AddPage(string name, DateTime creationDateTime) {
			PageInfo p = new PageInfo(name, this, PageStatus.Normal, creationDateTime);
			pages.Add(p);
			messages.Add(p, new List<Message>());
			return p;
		}

		public PageInfo RenamePage(PageInfo page, string newName) {
			page.Name = newName;
			return page;
		}

		public PageInfo SetStatus(PageInfo page, PageStatus status) {
			page.Status = status;
			return page;
		}

		public bool ModifyPage(PageInfo page, string title, string username, DateTime dateTime, string comment, string ctn, bool backup) {
			if(!pages.Contains(page)) return false;

			if(backup) Backup(page);

			List<PageContent> c = null;
			if(content.TryGetValue(page, out c)) {
				PageContent tmp = new PageContent(page, title, username, dateTime, comment, ctn);
				c[0] = tmp;
				return true;
			}
			else {
				PageContent tmp = new PageContent(page, title, username, dateTime, comment, ctn);
				content[page] = new List<PageContent>();
				content[page].Add(tmp);
				return true;
			}
		}

		public bool RollbackPage(PageInfo page, int revision) {
			List<PageContent> c = null;
			if(content.TryGetValue(page, out c)) {
				if(c.Count > 1 && revision <= c.Count - 2) {
					int idx = c.Count - revision;
					PageContent tmp = new PageContent(page, c[idx].Title, c[idx].User, c[idx].LastModified, c[idx].Comment, c[idx].Content);
					for(int i = 0; i < revision; i++) {
						c.Remove(c[c.Count - 1]);
					}
					c[0] = tmp;
					return true;
				}
				else return false;
			}
			else return false;
		}

		public bool DeleteBackups(PageInfo page, int revision) {
			List<PageContent> c = null;
			if(content.TryGetValue(page, out c)) {
				if(c.Count > 0) {
					PageContent tmp = c[0];
					c.Clear();
					c.Add(tmp);
					return true;
				}
				else return true;
			}
			else return false;
		}

		public bool RemovePage(PageInfo page) {
			bool done = pages.Remove(page);
			if(done) {
				content.Remove(page);
				messages.Remove(page);
			}
			return done;
		}

		public bool Rebind(PageInfo page, string[] cats) {
			for(int i = 0; i < categories.Count; i++) {
				List<string> tmp = new List<string>(categories[i].Pages);
				tmp.Remove(page.Name);
				categories[i].Pages = tmp.ToArray();
			}

			CategoryNameComparer comp = new CategoryNameComparer();
			for(int i = 0; i < cats.Length; i++) {
				CategoryInfo ci = new CategoryInfo(cats[i], this);
				for(int k = 0; k < categories.Count; k++) {
					if(comp.Compare(ci, categories[k]) == 0) {
						List<string> p = new List<string>(categories[k].Pages);
						p.Add(page.Name);
						categories[k].Pages = p.ToArray();
					}
				}
			}

			return true;
		}

		public Message[] GetMessages(PageInfo page) {
			List<Message> msg = null;
			if(messages.TryGetValue(page, out msg)) return msg.ToArray();
			else return new Message[0];
		}

		public int GetMessageCount(PageInfo page) {
			return CountMessages(new List<Message>(GetMessages(page)));
		}

		private int CountMessages(List<Message> messages) {
			int res = messages.Count;
			for(int i = 0; i < messages.Count; i++) {
				res += CountMessages(messages[i].Replies);
			}
			return res;
		}

		public bool AddMessage(PageInfo page, string username, string subject, DateTime dateTime, string body, int parent) {
			if(!pages.Contains(page)) return false;

			Message p = FindMessage(messages[page], parent);
			if(p == null) messages[page].Add(new Message(freeId, username, subject, dateTime, body));
			else p.Replies.Add(new Message(freeId, username, subject, dateTime, body));
			freeId++;
			return true;
		}

		private Message FindMessage(List<Message> messages, int id) {
			Message result = null;
			for(int i = 0; i < messages.Count; i++) {
				if(messages[i].ID == id) {
					result = messages[i];
				}
				if(result == null) {
					result = FindMessage(messages[i].Replies, id);
				}
				if(result != null) break;
			}
			return result;
		}

		public bool RemoveMessage(PageInfo page, int id, bool removeReplies) {
			if(!pages.Contains(page)) return false;

			Message msg = FindMessage(messages[page], id);

			if(msg == null) return false;

			if(!removeReplies) {
				Message anchestor = FindAnchestor(messages[page], msg.ID);
				if(anchestor != null) anchestor.Replies.AddRange(msg.Replies);
				else messages[page].AddRange(msg.Replies);
			}

			RemoveMessage(messages[page], msg);

			return true;
		}

		/// <summary>
		/// Removes a Message from a Message Tree.
		/// </summary>
		/// <param name="messages">The Message Tree.</param>
		/// <param name="msg">The Message to Remove.</param>
		/// <returns>True if the Message has been removed.</returns>
		private bool RemoveMessage(List<Message> messages, Message msg) {
			for(int i = 0; i < messages.Count; i++) {
				if(messages.Contains(msg)) {
					messages.Remove(msg);
					return true;
				}
				bool done = RemoveMessage(messages[i].Replies, msg);
				if(done) return true;
			}
			return false;
		}

		/// <summary>
		/// Finds the anchestor/parent of a Message.
		/// </summary>
		/// <param name="messages">The Messages.</param>
		/// <param name="id">The Message ID.</param>
		/// <returns>The anchestor Message or null.</returns>
		private Message FindAnchestor(List<Message> messages, int id) {
			Message result = null;
			for(int i = 0; i < messages.Count; i++) {
				for(int k = 0; k < messages[i].Replies.Count; k++) {
					if(messages[i].Replies[k].ID == id) {
						result = messages[i];
						break;
					}
					if(result == null) {
						result = FindAnchestor(messages[i].Replies, id);
					}
				}
				if(result != null) break;
			}
			return result;
		}

		public bool ModifyMessage(PageInfo page, int id, string username, string subject, DateTime dateTime, string body) {
			if(!pages.Contains(page)) return false;

			Message msg = FindMessage(messages[page], id);

			if(msg == null) return false;

			msg.Username = username;
			msg.Subject = subject;
			msg.DateTime = dateTime;
			msg.Body = body;
			return true;
		}

		public NavigationPath[] AllNavigationPaths {
			get { return new NavigationPath[0]; }
		}

		public NavigationPath AddNavigationPath(string name, PageInfo[] pages) {
			return null;
		}

		public NavigationPath ModifyNavigationPath(string name, PageInfo[] pages) {
			return null;
		}

		public bool RemoveNavigationPath(string name) {
			return false;
		}

		public Snippet[] AllSnippets {
			get { return new Snippet[0]; }
		}

		public Snippet AddSnippet(string name, string content) {
			return null;
		}

		public Snippet ModifySnippet(string name, string content) {
			return null;
		}

		public bool RemoveSnippet(string name) {
			return false;
		}

	}

}
